#include <ximera/input/dinput8/keyboard.hpp>
#include <ximera/input/dinput8/main.hpp>
#include <ximera/common/utils.hpp>

namespace ximera { namespace input { namespace dinput8
{
    void CI()
    {
        new Keyboard(NULL, SharedPtr<threading::MessageQueue>(), SharedObject<IDirectInput8>());
    }

    class XIMERA_PLUGIN_API KeyDownMessage: public threading::IMessage
    {
    public:
        KeyDownMessage(IKeyboard* sender, KeyCode const& keyCode): _sender(sender), _keyCode(keyCode)
        {
        }

        virtual void process()
        {
            try
            {
                if (_sender)
                    _sender->onKeyDown()(_sender, _keyCode);
            }
            catch(...)
            {
            }
        }

    private:
        IKeyboard* _sender;
        KeyCode _keyCode;
    };

    class XIMERA_PLUGIN_API KeyUpMessage: public threading::IMessage
    {
    public:
        KeyUpMessage(IKeyboard* sender, KeyCode const& keyCode): _sender(sender), _keyCode(keyCode)
        {
        }

        virtual void process()
        {
            try
            {
                if (_sender)
                    _sender->onKeyUp()(_sender, _keyCode);
            }
            catch(...)
            {
            }
        }

    private:
        IKeyboard* _sender;
        KeyCode _keyCode;
    };

    
    Keyboard::Keyboard(HWND hWnd, SharedPtr<threading::MessageQueue> const& msgqueue, SharedObject<IDirectInput8> const& dinput)
        : DeviceTemplate<IKeyboard>(hWnd, msgqueue, dinput)
    {
        XIMERA_CHECK(dinput, XIMERA_THROW(InvalidParamException, L"The dinput parameter can't be a Null Pointer"));
        XIMERA_CHECK(msgqueue, XIMERA_THROW(InvalidParamException, L"The msgqueue parameter can't be a Null Pointer"));

        IDirectInputDevice8* didevice = NULL;
        HRESULT hr = _dinput->CreateDevice(GUID_SysKeyboard, &didevice, NULL);
        XIMERA_CHECK(SUCCEEDED(hr), XIMERA_THROW(KeyboardException, HResultToString(hr)));
        _device.reset(didevice, false);

        hr = _device->SetDataFormat(&c_dfDIKeyboard); 
        XIMERA_CHECK(SUCCEEDED(hr), XIMERA_THROW(KeyboardException, HResultToString(hr)));

        hr = _device->SetCooperativeLevel(hWnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE); 
        XIMERA_CHECK(SUCCEEDED(hr), XIMERA_THROW(KeyboardException, HResultToString(hr)));

        init(0, 256);
        _buttonDeadZone = 0;

        _device->Acquire();
   }

    Keyboard::~Keyboard()
    {
        close();
    }

    Byte Keyboard::axisCount()
    {
        return 0;
    }

    Real Keyboard::axis(Byte axisIndex)
    {
        return 0;
    }

    Int16 Keyboard::axisInt(Byte axisIndex)
    {
        return 0;
    }

    Real Keyboard::axisRel(Byte axisIndex)
    {
        return 0;
    }

    Int16 Keyboard::axisRelInt(Byte axisIndex)
    {
        return 0;
    }

    UInt16 Keyboard::rumble(Byte rumbleIndex) const
    {
        return 0;
    }

    void Keyboard::rumble(Byte rumbleIndex, UInt16 value)
    {
    }

    void Keyboard::update()
    {
        std::vector<Byte>& btn0 = _button[0];
        std::vector<Byte>& btn1 = _button[1];

        HRESULT hr = _device->GetDeviceState(_buttonCount,(LPVOID)&btn1[0]);
        if (FAILED(hr))
        { 
            if ((hr == DIERR_INPUTLOST) || (hr == DIERR_NOTACQUIRED))
            {
                hr = _device->Acquire();

                if (FAILED(hr))
                    return;                                  

                hr = _device->GetDeviceState(_buttonCount,(LPVOID)&btn1[0]);
            }
        } 

        if (!_onKeyDown.empty() || !_onKeyUp.empty())
        {
            for (UInt16 I = 0; I < _buttonCount; ++I)
            {
                Byte& btn0v = btn0[I];
                Byte& btn1v = btn1[I];

                if (btn0v != btn1v)
                {
                    if (btn1v & 0x80)
                        _msgqueue->addMessage(new KeyDownMessage(this, KeyCode(I)));
                    else
                        _msgqueue->addMessage(new KeyUpMessage(this, KeyCode(I)));

                    btn0v = btn1v;
                }
            }
        }
    }
}}}
